// get the bit sizes associated with a float bitsize
struct owl_float_bit_sizes owl_get_float_bit_sizes(owl_umax type_size)
{
  // check params
  if (type_size != OWL_FLOAT_HALF_SIZE
      && type_size != OWL_FLOAT_SINGLE_SIZE
      && type_size != OWL_FLOAT_DOUBLE_SIZE)
    return (struct owl_float_bit_sizes) {0, 0, 0};
  
  // return the float components's sizes
  if (type_size == OWL_FLOAT_HALF_SIZE)
    return (struct owl_float_bit_sizes) {OWL_FLOAT_SIGN_SIZE,
                                         OWL_FLOAT_HALF_EXP_SIZE,
                                         OWL_FLOAT_HALF_FRAC_SIZE};
  if (type_size == OWL_FLOAT_SINGLE_SIZE)
    return (struct owl_float_bit_sizes) {OWL_FLOAT_SIGN_SIZE,
                                         OWL_FLOAT_SINGLE_EXP_SIZE,
                                         OWL_FLOAT_SINGLE_FRAC_SIZE};
  else // type_size == OWL_FLOAT_DOUBLE_SIZE
    return (struct owl_float_bit_sizes) {OWL_FLOAT_SIGN_SIZE,
                                         OWL_FLOAT_DOUBLE_EXP_SIZE,
                                         OWL_FLOAT_DOUBLE_FRAC_SIZE};
}

// function to check if an fmax is valid for number operations
owl_bool owl_check_float_bitsize(owl_umax bitsize)
{
  return (bitsize == OWL_FLOAT_HALF_SIZE
          || bitsize == OWL_FLOAT_SINGLE_SIZE
          || bitsize == OWL_FLOAT_DOUBLE_SIZE);
}

// function to check if an fmax is valid for number operations
owl_bool owl_check_float(owl_fmax num)
{
  return (num != OWL_PLUS_NAN && num != OWL_MINUS_NAN
          && num != OWL_PLUS_INF && num != OWL_MINUS_INF);
}

// gets the absolute value of a number
owl_float_op_result owl_abs(owl_fmax num)
{
  // check params
  if (owl_check_float(num) == owl_false)
    return (owl_float_op_result) {0.0f, owl_false};
  return (num < 0) ? (owl_float_op_result) {-num, owl_true} 
                   : (owl_float_op_result) {num, owl_true};
}

// gets the power of a float to the exponent of a signed integer
owl_float_op_result owl_pow_float_to_sint(owl_fmax base, owl_smax exponent)
{
  // check params
  if (owl_check_float(base) == owl_false || (base == 0.0f && exponent == 0))
    return (owl_float_op_result) {0.0f, owl_false};
  else if (base == 0.0f && exponent < 0)
    return (owl_float_op_result) {0.0f, owl_false};
  else if (base == 0.0f && exponent > 0)
    return (owl_float_op_result) {0.0f, owl_true};
  else if (exponent == 0)
    return (owl_float_op_result) {1.0f, owl_true};
  
  // calculate the result
  owl_fmax result = base;
  if (exponent < 0) {result = (base = 1 / base); exponent *= -1;}
  while (exponent-- > 0) result *= base;
  return (owl_float_op_result) {result, owl_check_float(result)};
}

// get the fractional part of a number
owl_float_op_result owl_float_frac(owl_fmax num)
{
  if (owl_check_float(num) == owl_false)
    return (owl_float_op_result) {0.0f, owl_false};
  
  // setup variables
  owl_bool is_negative = owl_false;
  if (num < 0) {num *= -1; is_negative = owl_true;}
  owl_fmax tmp = num;
  owl_umax pow10 = 1, exp10 = 0;
  
  // get maximum power of 10 that has the 
  // same digit count as the number (integer part)
  while (tmp > 10.0f) {
    tmp /= 10.0f;
    exp10++;
  }
  // fast power of 10
  for (owl_umax i = 0; i < exp10; i++)
    pow10 = (pow10 << 3) + (pow10 << 1);
  
  // substract most significative power of 10
  tmp = num / pow10;
  while (num > 1.0f) {
    // selection of the most significative number
    if (tmp >= 9.0f) tmp = 9.0f;
    else if (tmp >= 8.0f) tmp = 8.0f;
    else if (tmp >= 7.0f) tmp = 7.0f;
    else if (tmp >= 6.0f) tmp = 6.0f;
    else if (tmp >= 5.0f) tmp = 5.0f;
    else if (tmp >= 4.0f) tmp = 4.0f;
    else if (tmp >= 3.0f) tmp = 3.0f;
    else if (tmp >= 2.0f) tmp = 2.0f;
    else if (tmp >= 1.0f) tmp = 1.0f;
    else tmp = 0.0f;
    // substraction
    num = num - (tmp * pow10);
    pow10 /= 10;
    tmp = num / pow10;
  }
  
  // done!
  if (is_negative) num *= -1; 
  return (owl_float_op_result) {num, owl_check_float(num)};
}

// get integer part of a floating point number
owl_float_op_result owl_float_int(owl_fmax num)
{
  owl_fmax frac = owl_float_frac(num).f;
  return (owl_float_op_result) {num - frac, owl_check_float(num - frac)};
}

// get the lowest integer closer to a number
owl_float_op_result owl_floor(owl_fmax num)
{
  owl_float_op_result result = owl_float_int(num);
  if (result.success == owl_false)
    return (owl_float_op_result) {0.0f, owl_false};
  result.f = num >= 0.0f ? result.f : result.f - 1.0f;
  return (owl_float_op_result) {result.f, owl_check_float(result.f)};
}

// get the highest integer closer to a number
owl_float_op_result owl_ceil(owl_fmax num)
{
  // reuse owl_floor()
  owl_fmax result = owl_floor(num).f + 1.0f;
  return (owl_float_op_result) {result, owl_check_float(result)};
}

// get bits as a IEEE 754 floating point number
owl_float_op_result owl_bits_as_ieee754_float(owl_bitarr * bits)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false
      || owl_check_float_bitsize(bits -> num_bits) == owl_false)
    return (owl_float_op_result) {0.0f, owl_false};
  
  // save the original left_pad and num_bits from bitarr
  owl_umax left_pad = bits -> left_pad, num_bits = bits -> num_bits;
  // get the bitarr extra variables
  struct owl_bitarr_extra_vars a = owl_get_bitarr_extra_vars(bits);
  // set the exponent/fraction bit size variables
  struct owl_float_bit_sizes b = owl_get_float_bit_sizes(num_bits);
  owl_byte first_bit_mask = (1 << (OWL_BYTE_BITS - 1 - a.first_bit_offset));
  owl_char sign = ((bits -> arr -> ptr)[a.first_byte_pos] & first_bit_mask) != 0 ? -1 : 1;
  bits -> left_pad += b.sign_size;
  bits -> num_bits = b.exp_size;
  owl_smax exponent = owl_bits_as_off_bin_sint(bits).integer.s;  
  // check the exponent bits (inf/nan detection)
  if ((exponent + (1 << (b.exp_size - 1)) + 1) == (1 << b.exp_size)) {
    // restore the original bitarr values
    bits -> left_pad = left_pad;
    bits -> num_bits = num_bits;
    return (owl_float_op_result) {0.0f, owl_false};
  }
  bits -> left_pad += b.exp_size;
  bits -> num_bits = b.frac_size;
  // update the extra variables and update first_bit_mask
  a = owl_get_bitarr_extra_vars(bits);
  first_bit_mask = (1 << (OWL_BYTE_BITS - 1 - a.first_bit_offset));
  owl_fmax frac = 1.0f;
  owl_fmax frac_pow2 = 0.5f;
  for (owl_umax i = bits -> num_bits; i > 0;
       i--, frac_pow2 *= 0.5f, owl_circ_bitshift(bits, OWL_BITSHIFT_LEFT))
    frac += ((bits -> arr -> ptr)[a.first_byte_pos] & first_bit_mask) != 0 ? frac_pow2 : 0.0f;
  
  // join the pieces and restore bits's original variable's values
  owl_fmax result = sign * owl_pow_float_to_sint(2.0f, exponent).f * frac;
  bits -> left_pad = left_pad;
  bits -> num_bits = num_bits;
  return (owl_float_op_result) {result, owl_check_float(result)};
}

// get bits as IEEE 754 float number (not calculation, bitwise operations)
owl_float_op_result owl_get_float(owl_bitarr * bits)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false
      || owl_check_float_bitsize(bits -> num_bits) == owl_false)
    return (owl_float_op_result) {0.0f, owl_false};
    
  // if bitsize from source is larger than owl_fmax's bitsize
  // precision might be lost on the fractional part (kinda mid)
  // but errors will arise with the exponent (the awful part) if the number is too large
  // in the too low scenario the number will be just read as 0 and it might be enough
  // in other cases the process wont have data loss
  
  // number to return
  owl_byte result[sizeof(owl_fmax)] = {0};  
  // create bitarr to write the bits on result
  owl_bitarr tmp1 = {&((owl_bytarr) {result, sizeof(owl_fmax)}), 0, sizeof(owl_fmax) * OWL_BYTE_BITS};
  // save these variables beforehand
  owl_umax left_pad = bits -> left_pad, 
           num_bits = bits -> num_bits;
  // if bitsizes are equal (easy bit copy, wonder if I should keep it)
  //~ if (OWL_FLOAT_FMAX_SIZE == bits -> num_bits) {owl_write_bits(bits, &tmp1); goto end;}  
  // get bits's extra variables and system's & bits's float bit sizes
  struct owl_float_bit_sizes b = owl_get_float_bit_sizes(bits -> num_bits),
                             c = owl_get_float_bit_sizes(OWL_FLOAT_FMAX_SIZE);
  
  /////////////////////
  // write the sign bit
  bits -> num_bits = b.sign_size;
  owl_write_bits(bits, &tmp1);
  //////////////////////////
  // write the exponent bits
  bits -> left_pad += b.sign_size;
  bits -> num_bits = b.exp_size;
  tmp1.left_pad += c.sign_size;
  tmp1.num_bits = c.exp_size;
  // treat the exponent bits
  owl_umax exponent = owl_get_uint(bits).integer.u - (1 << (b.exp_size - 1));
  // check if the exponent it is too large
  if ((owl_smax) exponent >= (1 << (c.exp_size - 1)) - 1) {
    bits -> left_pad = left_pad; 
    bits -> num_bits = num_bits;
    return (owl_float_op_result) {0.0f, owl_false};
  } // needed to be able to read zeroes 
  else if ((owl_smax) exponent <= -(1 << (c.exp_size - 1))) exponent = 0;
  else exponent += (1 << (c.exp_size - 1)); // if the number is normal
  
  // reverse the bytes on exponent if system is in little endian
  if (OWL_SYS_ENDIAN == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {(void *) &exponent, sizeof(owl_umax)}));
  owl_bitarr tmp2 = {&((owl_bytarr) {(void *) &exponent, sizeof(owl_umax)}),
                     sizeof(owl_umax) * OWL_BYTE_BITS - c.exp_size, c.exp_size};
  owl_write_bits(&tmp2, &tmp1);
  
  //////////////////////////
  // write the fraction bits
  bits -> left_pad += b.exp_size;
  bits -> num_bits = b.frac_size;
  tmp1.left_pad += c.exp_size;
  tmp1.num_bits = c.frac_size;
  owl_write_bits(bits, &tmp1);
  
  // rounding fraction when overflow? (single float 12.38f to half float 12.375f case)
  // could add only a single bit to the last fraction bit if the rightmost bit is 1
  // (don't really know if it is a good idea)
  
  // consider system endian
  //~ end:
  if (OWL_SYS_ENDIAN == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {(void *) &result, sizeof(owl_fmax)}));
  
  // done! (restore the original variables)
  bits -> left_pad = left_pad; 
  bits -> num_bits = num_bits;
  return (owl_float_op_result) {*((owl_fmax *) result), owl_check_float(*((owl_fmax *) result))};
}

// get floats but only those byte aligned (made for big/little endian pulls)
owl_float_op_result owl_get_std_float(owl_bitarr * bits, owl_endian endian)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false
      || (bits -> left_pad % OWL_BYTE_BITS) != 0
      || owl_check_endian(endian) == owl_false)
    return (owl_float_op_result) {0.0f, owl_false};
  
  // check system endian
  if (endian == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {bits -> arr -> ptr + (bits -> left_pad / OWL_BYTE_BITS),
                                       bits -> num_bits / OWL_BYTE_BITS}));
  owl_float_op_result result = owl_get_float(bits);
  // reverse the reversal o7
  if (endian == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {bits -> arr -> ptr + (bits -> left_pad / OWL_BYTE_BITS),
                                       bits -> num_bits / OWL_BYTE_BITS}));
  return result;
}

// write a float from a bitarr into another bitarr
owl_bool owl_write_std_float_bitarr(owl_bitarr * src, owl_endian src_endian,
                                    owl_bitarr * dest, owl_endian dest_endian)
{
  // this function will basically be a copy and paste of the 
  // owl_get_float function just that the target float wont be
  // the system's float and it will be directly written into another bitarr
  
  // check params
  if (owl_check_bitarr_all(src, sizeof(owl_bitarr)) == owl_false
      || (src -> left_pad % OWL_BYTE_BITS) != 0
      || owl_check_float_bitsize(src -> num_bits) == owl_false
      || owl_check_endian(src_endian) == owl_false
      || owl_check_bitarr_all(dest, sizeof(owl_bitarr)) == owl_false
      || (dest -> left_pad % OWL_BYTE_BITS) != 0
      || owl_check_float_bitsize(dest -> num_bits) == owl_false
      || owl_check_endian(dest_endian) == owl_false)
    return owl_false;
    
  // same limitations mentioned on owl_get_float() apply here
  
  // save these variables beforehand
  owl_umax src_left_pad = src -> left_pad, 
           src_num_bits = src -> num_bits,
           dest_left_pad = dest -> left_pad, 
           dest_num_bits = dest -> num_bits;
  // get bits's extra variables and system's & bits's float bit sizes
  struct owl_float_bit_sizes b = owl_get_float_bit_sizes(src -> num_bits),
                             c = owl_get_float_bit_sizes(dest -> num_bits);
  
  // reverse the bytes on src if on little endian
  if (src_endian == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {src -> arr -> ptr + (src -> left_pad / OWL_BYTE_BITS),
                                       src -> num_bits / OWL_BYTE_BITS}));
  
  //////////////////////////////////////////////////
  // check exponent overflow before writing anything
  src -> left_pad += b.sign_size;
  src -> num_bits = b.exp_size;
  dest -> left_pad += c.sign_size;
  dest -> num_bits = c.exp_size;
  // treat the exponent bits
  owl_umax exponent = owl_get_uint(src).integer.u - (1 << (b.exp_size - 1));
  // check if the exponent it is too large
  if ((owl_smax) exponent >= (1 << (c.exp_size - 1)) - 1) {
    src -> left_pad = src_left_pad; 
    src -> num_bits = src_num_bits;
    dest -> left_pad = dest_left_pad; 
    dest -> num_bits = dest_num_bits;
    return owl_false;
  } // needed to be able to read zeroes 
  else if ((owl_smax) exponent <= -(1 << (c.exp_size - 1))) exponent = 0;
  else exponent += (1 << (c.exp_size - 1)); // if the number is normal
  
  // reset src and dest vars to write everything
  src -> left_pad = src_left_pad; 
  src -> num_bits = src_num_bits;
  dest -> left_pad = dest_left_pad; 
  dest -> num_bits = dest_num_bits;
  
  /////////////////////
  // write the sign bit
  src -> num_bits = b.sign_size;
  owl_write_bits(src, dest);
  //////////////////////////
  // write the exponent bits
  src -> left_pad += b.sign_size;
  src -> num_bits = b.exp_size;
  dest -> left_pad += c.sign_size;
  dest -> num_bits = c.exp_size;
  if (OWL_SYS_ENDIAN == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {(void *) &exponent, sizeof(owl_umax)}));
  owl_bitarr tmp1 = {&((owl_bytarr) {(void *) &exponent, sizeof(owl_umax)}),
                     sizeof(owl_umax) * OWL_BYTE_BITS - c.exp_size, c.exp_size};
  owl_write_bits(&tmp1, dest);
  //////////////////////////
  // write the fraction bits
  src -> left_pad += b.exp_size;
  src -> num_bits = b.frac_size;
  dest -> left_pad += c.exp_size;
  dest -> num_bits = c.frac_size;
  owl_write_bits(src, dest);
  
  // restore the original variables
  src -> left_pad = src_left_pad; 
  src -> num_bits = src_num_bits;  
  dest -> left_pad = dest_left_pad; 
  dest -> num_bits = dest_num_bits;  
  
  // consider system endian
  if (src_endian == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {src -> arr -> ptr + (src -> left_pad / OWL_BYTE_BITS),
                                       src -> num_bits / OWL_BYTE_BITS}));
  if (dest_endian == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {dest -> arr -> ptr + (dest -> left_pad / OWL_BYTE_BITS),
                                       dest -> num_bits / OWL_BYTE_BITS}));
  
  // done!
  return owl_true;
}

// write std float into dest bitarr, assumes the system uses the std float representation
owl_bool owl_write_std_float(owl_fmax num, owl_bitarr * dest, owl_endian endian)
{
  // check params
  if (owl_check_float(num) == owl_false
      || owl_check_bitarr_all(dest, sizeof(owl_bitarr)) == owl_false
      || owl_check_endian(endian) == owl_false)
    return owl_false;
    
  // reuse owl_write_std_float_bitarr()
  owl_bitarr tmp = {&(owl_mem_space) {(void *) &num, sizeof(owl_fmax)}, 0, sizeof(owl_fmax) * OWL_BYTE_BITS}; 
  return owl_write_std_float_bitarr(&tmp, OWL_SYS_ENDIAN, dest, endian);
}

// get std float and prepare bits to read the next thing right away
owl_float_op_result owl_get_std_float_mw(owl_bitarr * bits, owl_endian endian)
{
  owl_float_op_result result = owl_get_std_float(bits, endian);
  if (result.success == owl_true)
    owl_mw_upd_vars_internal(bits);
  return result;
}

// get float16 with moving window
owl_float_op_result owl_get_std_float16_mw(owl_bitarr * bits, owl_endian endian)
{
  bits -> num_bits = 16;
  return owl_get_std_float_mw(bits, endian);
}

// get float32 with moving window
owl_float_op_result owl_get_std_float32_mw(owl_bitarr * bits, owl_endian endian)
{
  bits -> num_bits = 32;
  return owl_get_std_float_mw(bits, endian);
}

// get float64 with moving window
owl_float_op_result owl_get_std_float64_mw(owl_bitarr * bits, owl_endian endian)
{
  bits -> num_bits = 64;
  return owl_get_std_float_mw(bits, endian);
}

// writes a std float into dest and updates dest to write to the next location
owl_bool owl_write_std_float_mv(owl_fmax num, owl_bitarr * dest, owl_endian endian)
{
  if (owl_write_std_float(num, dest, endian) == owl_true) {
    owl_mw_upd_vars_internal(dest);
    return owl_true;
  }
  return owl_false;
}

// writes float16 with moving window 
owl_bool owl_write_std_float16_mv(owl_fmax num, owl_bitarr * dest, owl_endian endian)
{
  dest -> num_bits = 16;
  return owl_write_std_float_mv(num, dest, endian);
}

// writes float32 with moving window 
owl_bool owl_write_std_float32_mv(owl_fmax num, owl_bitarr * dest, owl_endian endian)
{
  dest -> num_bits = 32;
  return owl_write_std_float_mv(num, dest, endian);
}

// writes float64 with moving window 
owl_bool owl_write_std_float64_mv(owl_fmax num, owl_bitarr * dest, owl_endian endian)
{
  dest -> num_bits = 64;
  return owl_write_std_float_mv(num, dest, endian);
}
